//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2005-2007
//
// OziExplorer RTE (routes) parser.
//

# include "StdAfx.h"
# include "Map.h"
# include "Types.h"
# include "Datum.h"
# include "Load.h"
# include "Log.h"
# include "Status.h"
# include "ParseHelpers.h"
# include "OziHelpers.h"
# include "fast_floor.h"
# include "Globals.h"

class COziRteLoader : public CMapLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);

  private:
	void _ParseRoute    (const char * _strData, const char * _strEnd);
	void _ParseWaypoint (const char * _strData, const char * _strEnd);

  protected:
	virtual const char * ParseLine (const char * _p);

	void _FlushRoute (const char * _pRouteEnd);

	map_t * m_pMap;
	const char * m_pStart, * m_pEnd;

	// The counter of current line.
	size_t m_cFileLine;
	//
	size_t m_cOrdinal;

	const CTransformationBase * m_pTransformation;

	file_refs_t m_file_refs;

	const char *  m_pRouteBegin;
	string_t      m_strRouteName;
	string_t      m_strRouteDescription;
	COLORREF      m_colRoute;
	bool          m_bRouteColor;
	points_t      m_points;
	routepoints_t m_rpoints;
};

DECLARE_WPTRK_LOADER (COziRteLoader, "ozi-rte", "rte", "OziExplorer routes (*.rte)", ftiRoutes);

bool COziRteLoader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (! CheckAnsiFile (_pData, _cFileSize, true))
		return false;

	return
		HasPrefix (_pData, _pData + _cFileSize, "OziExplorer Route File", 22) ||
		HasPrefix (_pData, _pData + _cFileSize, "OziExplorer CE Route File", 25);
}

void COziRteLoader::_FlushRoute (const char * _pRouteEnd) {
	if (m_points.empty ())
		return;

	//
	// Create route.
	//
	rgn_t & rgn = m_pMap->CreateRgn (rkPolyline);
	rgn.SetRouteType ();

	rgn.pFileRef  = & m_file_refs.back ();
	rgn.cBeginPos = m_pRouteBegin - m_pStart;
	rgn.cEndPos   = _pRouteEnd    - m_pStart;
	rgn.cFileLine = -1;
//	rgn.cOrdinal = m_cOrdinal ++;

	// Name.
	rgn.strLabel.swap (m_strRouteName);
	// Comment.
	rgn.strComment.swap (m_strRouteDescription);

	rgn.elements.push_back (rgn_element_t ());
	rgn_element_t & element = rgn.elements.back ();
	element.cFileOffset = rgn.cBeginPos;

	//
	element.cLevel = 0;
	element.points.swap (m_points);

	route_info_t * const pRouteExtra = rgn.pExtra->wtr.CreateRoute ();
	pRouteExtra->points.swap (m_rpoints);

	if (m_bRouteColor) {
		pRouteExtra->wFlags |= route_info_t::defLineColor;
		pRouteExtra->colLine = m_colRoute;
	}
}

void COziRteLoader::_ParseRoute (const char * _strData, const char * _strEnd) {
	_FlushRoute (_strData);

	//
	// Parse fields.
	//

	const char * const strRouteNumber = OziFindNextField (_strData,       _strEnd);
	const char * const strName        = OziFindNextField (strRouteNumber, _strEnd);
	const char * const strDescription = OziFindNextField (strName,        _strEnd);
	const char * const strColor       = OziFindNextField (strDescription, _strEnd);

	m_pRouteBegin = _strData;
	m_points.clear ();
	m_rpoints.clear ();

	// Name.
	OziParseString (strName, strDescription, m_strRouteName);

	// Description.
	if (strColor != _strEnd)
		OziParseString (strDescription, strColor, m_strRouteDescription);
	else
		m_strRouteDescription.clear ();

	// Color.
	unsigned long ulColor = 0;
	const char * p;
	if (ParseDecU (strColor, _strEnd, & p, ulColor)) {
		m_colRoute = ulColor;
		m_bRouteColor = true;
	} else
		m_bRouteColor = false;
}

void COziRteLoader::_ParseWaypoint (const char * _strData, const char * _strEnd) {
	//
	// Parse fields.
	//

	const char * const strRouteNumber         = OziFindNextField (_strData,            _strEnd);
	const char * const strNumber              = OziFindNextField (strRouteNumber,      _strEnd);
	const char * const strWpNumber            = OziFindNextField (strNumber,           _strEnd);
	const char * const strName                = OziFindNextField (strWpNumber,         _strEnd);
	const char * const strLat                 = OziFindNextField (strName,             _strEnd);
	const char * const strLon                 = OziFindNextField (strLat,              _strEnd);
	const char * const strDateTime            = OziFindNextField (strLon,              _strEnd);
	const char * const strSymbol              = OziFindNextField (strDateTime,         _strEnd);
	const char * const strStatus              = OziFindNextField (strSymbol,           _strEnd);
	const char * const strMapDisplayFormat    = OziFindNextField (strStatus,           _strEnd);
	const char * const strFgColor             = OziFindNextField (strMapDisplayFormat, _strEnd);
	const char * const strBgColor             = OziFindNextField (strFgColor,          _strEnd);
	const char * const strDescription         = OziFindNextField (strBgColor,          _strEnd);
	const char * const strPointerDirection    = OziFindNextField (strDescription,      _strEnd);
	const char * const strGarminDisplayFormat = OziFindNextField (strPointerDirection, _strEnd);

	// Name.
	string_t strWptName;
	OziParseString (strName, strLat, strWptName);

	// Description.
	string_t strWptDescription;
	if (strPointerDirection != _strEnd)
		OziParseString (strDescription, strPointerDirection, strWptDescription);
	else
		strWptDescription.clear ();

	// Position.
	const char * pEnd = NULL;
	point_t point;
	if (
		! ParseReal (strLat, _strEnd, & pEnd, point.y) ||
		! ParseReal (strLon, _strEnd, & pEnd, point.x)
	) {
		ReportWarning (_strData - m_pStart, "Unexpected format of waypoint data.");
		return;
	}

	//
	// Add the waypoint to the map.
	//

	rgn_t & rgn = m_pMap->CreateRgn (rkPoint);
	rgn.SetWaypointType ();

	rgn.pFileRef  = & m_file_refs.back ();
	rgn.cBeginPos = _strData - m_pStart;
	rgn.cEndPos   = _strEnd - m_pStart;
	rgn.cFileLine = m_cFileLine;
//	rgn.cOrdinal  = m_cOrdinal ++;

	rgn.strLabel.swap (strWptName);
	rgn.strComment.swap (strWptDescription);

	rgn.origins.push_back (rgn_origin_t ());
	rgn_origin_t & origin = rgn.origins.back ();
	origin.cLevel      = 0;
	origin.cFileOffset = _strData - m_pStart;
	origin.point       = point;

	// Extra properties.
	waypoint_info_t * const pWptExtra = rgn.pExtra->wtr.CreateWaypoint ();
	
	// Date and Time.
	double dfDateTime = 0;
	if (ParseReal (strDateTime, _strEnd, & pEnd, dfDateTime)) {
		if (dfDateTime != 0) {
			pWptExtra->wFlags |= waypoint_info_t::defTime;
			pWptExtra->dtTime = dfDateTime;
		}
	}

	// Text color.
	unsigned long ulColor = 0;
	const char * p;
	if (ParseDecU (strFgColor, strBgColor, & p, ulColor)) {
		pWptExtra->wFlags |= waypoint_info_t::defTextColor;
		pWptExtra->colText = ulColor;
	}

	// Back color.
	unsigned long ulBackColor = 0;
	if (ParseDecU (strBgColor, strDescription, & pEnd, ulBackColor)) {
		pWptExtra->wFlags |= waypoint_info_t::defBackColor;
		pWptExtra->colBack = ulBackColor;
	}

	//
	// Add waypoint to route.
	//

	m_points.push_back (point);

	m_rpoints.push_back (routepoint_info_t ());
	routepoint_info_t & rpoint = m_rpoints.back ();
	rpoint.pWaypoint = & rgn;
}

const char * COziRteLoader::ParseLine (const char * _p) {
	const char * const pNextLine = FindNextLine (_p, m_pEnd);

	if (_p [0] != '\n' && _p [0] != '\r') {
		if (m_cFileLine == 2) {
			// Get datum.
			const char * strDatumEnd = _p;
			while (strDatumEnd < pNextLine) {
				if (* strDatumEnd == ',' || * strDatumEnd == '\r' || * strDatumEnd == '\n')
					break;
				++ strDatumEnd;
			}
			const string_t strDatum (_p, strDatumEnd);

			if (! FindOziDatumToWGS84 (strDatum.c_str (), m_pTransformation)) {
				m_pTransformation = NULL;
				ReportWarning ("Some unsupported datum is used: '%s'. No datum conversion performed.", strDatum.c_str ());
			}
		} else if (m_cFileLine >= 5) {
			if (m_cFileLine == 5)
				m_pMap->cHeaderLen = _p - m_pStart;

			if (* _p == 'R')
				_ParseRoute (_p, pNextLine);
			else if (* _p == 'W')
				_ParseWaypoint (_p, pNextLine);
		}
	}

	// Count lines.
	++ m_cFileLine;

	return pNextLine;
}

LoadStatus_t COziRteLoader::Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap) {
	m_pMap = _pMap;
	m_pStart = _pData;
	m_pEnd = _pData + _cFileSize;

	//
	// Parse .rte file.
	//

	m_cFileLine = 1;
	m_cOrdinal = 0;

	m_pRouteBegin = NULL;
	m_points.clear ();
	m_rpoints.clear ();

	m_pTransformation = NULL;

	m_file_refs.clear ();
	m_file_refs.push_back (file_ref_t ());
	m_file_refs.back ().strFileName = _strFileName;
	m_file_refs.back ().bAttachment = true;

	SetProgressStatus (0);

	const char * pLine = _pData;
	do {
		if (g_bStop)
			return lsInterrupted;
		pLine = ParseLine (pLine);
		SetProgressStatus (fast_floor ((50.f*(pLine - _pData))/_cFileSize + .5f));
	} while (pLine < m_pEnd);

	_FlushRoute (m_pEnd);

	SetProgressStatus (50);

	// Convert datum.
	if (m_pTransformation) {
		ReportText ("Performing datum conversion to WGS84...");
		m_pMap->TransformPoints (* m_pTransformation);
	}

	m_pMap->file_refs.swap (m_file_refs);

	// Suppress warning about empty Zoom etc.
	m_pMap->bNoHeader = true;

	return lsOK;
}

////////////////////////////////////////////////////////////////////

class COziRt2Loader : public COziRteLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;

  private:
	virtual const char * ParseLine (const char * _p);

	void _ParseRoute    (const char * _strData, const char * _strEnd);
	void _ParseWaypoint (const char * _strData, const char * _strEnd);
};

DECLARE_WPTRK_LOADER (COziRt2Loader, "ozi-rt2", "rt2", "OziCE routes (*.rt2)", ftiRoutes);

bool COziRt2Loader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (! CheckAnsiFile (_pData, _cFileSize, true))
		return false;

	return HasPrefix (_pData, _pData + _cFileSize, "H1,OziExplorer CE Route2 File", 29);
}

const char * COziRt2Loader::ParseLine (const char * _p) {
	const char * const pNextLine = FindNextLine (_p, m_pEnd);

	if (_p [0] != '\n' && _p [0] != '\r') {
		if (HasPrefix (_p, pNextLine, "H2,", 3)) {
			// Get datum.
			const char * strDatumEnd = _p + 3;
			while (strDatumEnd < pNextLine) {
				if (* strDatumEnd == ',' || * strDatumEnd == '\r' || * strDatumEnd == '\n')
					break;
				++ strDatumEnd;
			}
			const string_t strDatum (_p + 3, strDatumEnd);

			if (! FindOziDatumToWGS84 (strDatum.c_str (), m_pTransformation)) {
				m_pTransformation = NULL;
				ReportWarning ("Some unsupported datum is used: '%s'. No datum conversion performed.", strDatum.c_str ());
			}
		} else {
			if (m_pMap->cHeaderLen == 0)
				m_pMap->cHeaderLen = _p - m_pStart;

			if (HasPrefix (_p, pNextLine, "H3,", 3)) {
				_ParseRoute (_p, pNextLine);
			} else if (HasPrefix (_p, pNextLine, "W,", 2)) {
				_ParseWaypoint (_p, pNextLine);
			}
		}
	}

	// Count lines.
	++ m_cFileLine;

	return pNextLine;
}

void COziRt2Loader::_ParseRoute (const char * _strData, const char * _strEnd) {
	_FlushRoute (_strData);

	//
	// Parse fields.
	//

	const char * const strName        = OziFindNextField (_strData,       _strEnd);
	const char * const strDescription = OziFindNextField (strName,        _strEnd);
	const char * const strColor       = OziFindNextField (strDescription, _strEnd);

	m_pRouteBegin = _strData;
	m_points.clear ();
	m_rpoints.clear ();

	// Name.
	OziParseString (strName, strDescription, m_strRouteName);

	// Description.
	if (strColor != _strEnd)
		OziParseString (strDescription, strColor, m_strRouteDescription);
	else
		m_strRouteDescription.clear ();

	// Color.
	unsigned long ulColor = 0;
	const char * p;
	if (ParseDecU (strColor, _strEnd, & p, ulColor)) {
		m_colRoute = ulColor;
		m_bRouteColor = true;
	} else
		m_bRouteColor = false;
}

void COziRt2Loader::_ParseWaypoint (const char * _strData, const char * _strEnd) {
	//
	// Parse fields.
	//

	const char * const strName   = OziFindNextField (_strData, _strEnd);
	const char * const strLat    = OziFindNextField (strName,  _strEnd);
	const char * const strLon    = OziFindNextField (strLat,   _strEnd);
	const char * const strStatus = OziFindNextField (strLon,   _strEnd);

	// Name.
	string_t strWptName;
	OziParseString (strName, strLat, strWptName);

	// Position.
	const char * pEnd = NULL;
	point_t point;
	if (
		! ParseReal (strLat, _strEnd, & pEnd, point.y) ||
		! ParseReal (strLon, _strEnd, & pEnd, point.x)
	) {
		ReportWarning (_strData - m_pStart, "Unexpected format of waypoint data.");
		return;
	}

	//
	// Add the waypoint to the map.
	//

	rgn_t & rgn = m_pMap->CreateRgn (rkPoint);
	rgn.SetWaypointType ();

	rgn.pFileRef  = & m_file_refs.back ();
	rgn.cBeginPos = _strData - m_pStart;
	rgn.cEndPos   = _strEnd - m_pStart;
	rgn.cFileLine = m_cFileLine;
//	rgn.cOrdinal  = m_cOrdinal ++;

	rgn.strLabel.swap (strWptName);

	rgn.origins.push_back (rgn_origin_t ());
	rgn_origin_t & origin = rgn.origins.back ();
	origin.cLevel      = 0;
	origin.cFileOffset = _strData - m_pStart;
	origin.point       = point;

	// Extra properties.
	waypoint_info_t * const pWptExtra = rgn.pExtra->wtr.CreateWaypoint ();
	
	//
	// Add waypoint to route.
	//

	m_points.push_back (point);

	m_rpoints.push_back (routepoint_info_t ());
	routepoint_info_t & rpoint = m_rpoints.back ();
	rpoint.pWaypoint = & rgn;
}
